\chapter{软件实现}
\section{软件简介}
本文软件基于VTK和QT开发。VTK是一款已有近三十年历史的开源CFD框架\textsuperscript{\cite{VTK}}，它提供了常用的可视化和后处理方法。QT是一个跨平台GUI框架，其大量使用面向对象与模块化设计，有丰富的API与模板，同时支持底层渲染的自定义（如使用OpenGL），附带大量的说明文档，且提供了IDE、GUI开发、国际化和文档管理等软件，功能强大。QT拥有商业版和社区开源版，前者主要包含QT官方的售后技术支持，而后者拥有绝大部分QT的功能，足够普通软件使用。本文软件使用了QT的开源社区版。

在此基础上，软件使用C++编程语言编写，使用了部分C++17特性。软件在Windows 10平台上开发，使用了微软Visual Studio作为IDE进行工程管理和开发，同时编写了CMakeLists以支持使用CMAKE进行跨平台编译发布。软件以工程的标准开发，将设计模式和工作效率视为重要的评判标准。为增加代码复用性，软件使用了大量模块化设计。以下内容便是软件各个模块的介绍，并且描述了模块之间协同运作的方式。

\begin{figure}[H]
    \centering
    \includegraphics[width=0.6\textwidth]{./images/placeholder}
    \caption{软件主界面}
\end{figure}

\subsection{VTK的使用}
VTK使用C++开发，并且大量使用面向对象思维实现，目前最新版本是VTK9，但因为其改动较大，迭代版本不足，软件不够稳定，因此本文软件使用上一代的最终版本8.2.0。

要把场景渲染到屏幕，首先需要一个载体，这在操作系统中对应了一个窗口。VTK自己实现了跨平台的窗口系统，其底层是基于不同操作系统的接口（如window的WIN32接口，Unix的X11接口等）封装实现的，因此无需其他窗口库，VTK便可以实现简单渲染程序的开发。但是VTK的窗口系统并不包含GUI的支持，因此该系统没有用在实际的软件中，但是该系统在进行测试时大量运用了。

要使用VTK完成最基本的渲染，需要几项基本的对象。
\begin{itemize}
    \item 渲染窗口（vtkRenderWindow）。作为渲染的目标，最终展示渲染效果的容器。
    \item 渲染器（vtkRenderer）。用于实际地渲染场景。
    \item 物体（vtkProp）。包括了各类可以出现在场景中的物体，如3D数据、文本、坐标轴等等。通过vtkProp可以控制物体在场景中的位置、大小、旋转和材质等物体属性。
\end{itemize}

物体数据的来源可以是算法生成（VTK提供了一些基本图形的生成算法，其类名以“Source”结尾），也可以是外部导入。VTK提供了一系列数据结构，其中主要使用的是结构数据和非结构数据，从各类文件读入数据的目标便是解析文件数据并将其转换为VTK的数据结构。VTK也提供了部分文件的

VTK提供了后处理算法类，称为“Filter”。Filter的输入是VTK数据结构，输出可能包含VTK结构，也可能包含一些额外的信息。

VTK并不是万能而完善的。它是一个开源软件，由社区共同维护，并且截止目前依旧在不断更新，其软件本身不够稳定，有些的接口设计或代码逻辑欠妥（如使用了不恰当的硬编码），加上文档不够详尽，导致开发时常遇到难以解决的困难。为解决问题，便要发挥开源的优势，需要准备VTK的源码，遇到不解处时可以翻阅代码帮助排除错误。

\subsection{QT的使用}
QT也是C++开发的框架，其使用了视图——模型——控制器模式（Model View Controller Model，简称MVC模式），将数据和界面分离。QT的界面开发实际是在编写一个窗口类，其控制了窗口的控件（如文字、按钮、菜单等）和事件处理，而QT提供的界面设计工具Designer，使用它无需使用C++代码编写类，而是通过直接的选择、移动、拖放等操作设计界面，而后使用QT的uic工具将界面文件转换为C++类，从而参与项目的编译。

QT使用信号/槽机制实现事件处理。需要发送事件时，便可以产生一个信号，而需要接受某种事件时，便准备一个槽。而信号传输到哪个槽需要额外指定，通过connect函数进行连接。这样的好处是，事件产生者负责产生事件，无需关心谁会收到事件；而事件接收者同理，无需知道谁会产生事件，加上事件和槽会进行签名对比，不匹配时不会触发槽，这提高了代码复用性和应用程序的安全性。一个控件可以产生许多事件，如按钮可以产生按下事件，复选框可以产生勾选/取消勾选事件等，通过槽来监听这些信号的产生便可以实现界面交互。

要将VTK集成到QT中，最主要的是将VTK渲染的内容镶嵌到QT中，并且让VTK接管一部分鼠标和键盘事件。VTK中实现了基于QT窗口类的VTK渲染窗口，这便实现了QT和VTK的连接。

\section{模块实现}
\subsection{抽象工厂模式}
软件在模块设计上使用了抽象工厂模式，因为软件中主要这涉及到两个工厂：IO工厂和Filter工厂，分别对应文件读写和后处理。两个工厂拥有类似的结构，因此为了代码复用，抽象工厂模式将工厂重复的代码分离出工厂的基类，称为抽象工厂，并且抽象工厂负责具体工厂的创建和管理。在此基础上添加公共的接口用于创建产品，同时需要传入产品签名用于确认使用哪个工厂，以及创建哪个产品。

具体的，IO模块使用了工厂模式，各类文件的读取都对应了一个类，这些类都继承一个IO基类，而IO基类是一个产品，其子类自然也是产品。在使用之前需要先创建工厂，这里会为每一种文件读写类创建一一对应的工厂，创建工厂时要指定一个唯一字符串用作签名。而后便使用公共接口，传入签名来创建对应的对象。Filter模块和IO模块的做法基本一致，这里不做赘述。

\subsection{异步}
软件中涉及GUI渲染、场景渲染和后处理三个相对独立的业务。GUI渲染中，QT使用信号进行异步编程，事件通过信号和信号函数在单独的线程处理；场景渲染由VTK核心处理，并使用VTK实现的事件机制实现异步；而在后处理与文件读取中，软件使用了C++11特性中的async、promise和future实现异步处理。

C++11在标准库中引入了线程以及一系列相关的新功能。async函数可以异步调用指定函数，promise用于记录一个异步函数的执行状态和结果，供其他线程使用，而通过promise可以获取其对应的future，访问其存储的数据也是通过future实现的。

进行一次异步操作的步骤如下。

\begin{enumerate}
    \item 创建一个promise。
    \item 获取这个promise的future。
    \item 使用async执行一个异步函数，并传入这个promise。
    \item 主线程继续执行其他业务，或是等待执行结束；异步函数在新的线程中执行，执行完成后将
    promise设置为完成状态。
    \item 主线程通过future获取执行结果。
\end{enumerate}

可见，异步方式可以避免主线程的阻塞，在读取大文件时可以避免长时间卡死，改善用户体验。

\subsection{深度冲突（z-fighting)}
可视化中同一个物体可能会同时显示散点、网格和面等，因为点和边显然和面重叠，在进行深度测试时重叠处的深度相同，而因为浮点数精度问题会导致一些地方某个物体在前，另一些地方另一个物体在前，导致线段和点若隐若现、断断续续，这种问题称为z-fighting。一般来说，我们希望点和网格是显示在面前方，因为反之会导致点和网格被面完全遮挡无法看见。解决z-fighting可以使用深度偏移（Depth Bias），在计算深度时增加深度偏移量，让重叠物体的深度错开，但是这需要每个物体的深度偏移适中，否则无法达到效果，甚至可能导致场景错位。因为这里只需要点和线段在在面之前，可以使用一个简单的方法：在渲染点和线段时，给定一个深度偏移量，让深度略微小一些，可以挡住面；而渲染面时不设置深度偏移。这样可以解决点线与面之间的冲突，但是不能解决面与面之间的冲突。

\subsection{库管理}
本程序使用了一些外部库，如提供了日志和设计模式的hive实验室公共库、其依赖的boost库、包含支持并行访问数据结构的Intel tbb库、用于设计测试的Google Test库以及用作核心框架的VTK库。C/C++开发中的库管理较为困难，因为其具有多类编译器，这些编译器的标准各不相同，导致其编译结果常常不通用。为解决外部库不统一的问题，本文软件使用了NuGet管理这些库。NuGet是微软为.NET Framework开发的C\#库管理器，其通过统一的方式打包发布库，供互联网上的用户使用。NuGet也有为C/C++开发的版本，使用它可以避免库版本不统一的问题。

\subsection{链接库}
本软件既可以独立运行，又可以发布为库共其他软件集成。链接库主要有两种形式：静态链接库和动态链接库，不同的平台其文件的扩展名也不同（见表\ref{fig:link_library_extensions}）。
\begin{table}
    \centering
    \caption{不同平台的链接库扩展名}
    \label{fig:link_library_extensions}
    \begin{tabular}{ccc}
        \hline
        扩展名 & 静态链接库 & 动态链接库\\
        \hline
        Windows & .lib & .dll \\
        Unix/Linux & .a & .so \\
        \hline
    \end{tabular}
\end{table}

链接库中存有变量、函数和类，需要通过头文件（扩展名一般为.h/.hpp）进行访问。静态链接库中是编译好的代码，在被其他程序使用时，会随着编译一同打包入应用程序中，不可再分离；而动态库也可以被其他程序使用，但是在编译时不再打包入应用程序，而是在应用程序启动时，由操作系统负责载入、管理dll，再由应用程序使用。动态链接库方便更新，只需要替换动态链接库文件即可更新执行代码，而不需要重新编译程序，并且动态链接库能解决相同代码重复链接的问题，因此本软件使用了动态链接库。Visual Studio中可以添加“解决方案配置”实现多种编译环境，在添加生成动态链接库的环境后，在生成选项中取消了Visualization模块的生成，并配置其他模块生成动态链接库到指定文件夹。

编译动态链接库时需要指定对外导出的接口和类，这里在每个模块中使用宏进行控制。当进行普通编译时，无需设置导出符号，因此导出的宏为空；而在编译链接库时，导出的宏将成为导出符号。导出动态链接库时，如果该动态链接库有其他依赖的库，需要同时导出一个“导出文件”

\subsection{说明文档}
软件可以以链接库的形式发布，自然也需要配套的说明文档才可以供其他人使用。说明文档需要包含从项目整体的介绍、各个模块的说明到每个接口的具体用法等等内容，他人通过阅读说明文档了解软件的功能和使用方法，本软件使用了Doxygen制作说明文档。Doxygen是一款开源的、跨平台的代码文档生成工具，它支持各种主流语言的文档生成，可以生成在线文档（HTML格式）和离线文档（\LaTeX 格式），并且支持提取代码间的引用关系便于快速找到被引用的内容。

要使用Doxygen，需要在代码中以Doxygen要求的格式书写注释，便可以在生成被注释内容的文档。使用Doxygen的好处是无需单独书写文档，只需在编写代码时书写注释即可，这样也便于处理代码改变导致的文档更新问题；此外通过Doxygen生成的文档具有丰富的交叉引用，可以方便索引到被引用的内容，阅读起来极为方便。

\textcolor{red}{这里可以加上Doxygen的具体配置和理由}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.6\textwidth]{./images/placeholder}
    \caption{Doxygen文档主页和接口说明}
\end{figure}

\subsection{国际化}
本软件包含国际化的功能，并且实现了中文和英文的国际化。国际化的主要工作是翻译，QT提供了一套完整的国际化方案。在QT中，国际化的文本对照存放在.ts文件中，.ts文件是XML文件，可以通过QT提供的Linguist软件进行编辑，而后导出为二进制的.qm文件，其不再包含冗余的内容。.在开发中，QT提供了翻译函数，使用翻译函数获取翻译的字符串，而生成.ts时使用的lupdate工具会自动收集翻译函数覆盖的词条，将这些词条整理入.ts文件中供开发者翻译。QT提供了qTranslator类用于管理应用程序的翻译，通过它载入由lrelease工具生成的.qm文件便可以实现翻译的效果。

\subsection{用户界面设计} 
如图\ref{fig:GUI}所示，用户界面使用了较常规的设计，包含了标题栏、菜单栏、工具栏、配置窗口、采点窗口和图像区域。标题栏包括文件菜单，用于控制文件读取导出和关闭，还包含查看文件信息的按钮以及关闭程序的按钮；语言菜单，用于切换软件语言。工具栏包含了文件操作，以及交互模式的切换，从左到右分别为轨迹球、摇杆和平移。配置窗口最上方是渲染模式切换，可以为3D渲染或图表渲染；3D渲染模型下，可以点击下方的按钮快速重置相机到原点，并朝向特定方向（从左向右分别为\textcolor{red}{（相机这里还有bug）x轴，y轴，z轴和对角方向}，而后是各类可视化、后处理的选项，包括了散点、网格、面、矢量箭头、边界、等势面/等值线及其值标记和图例、流线和帧率的显示开关和参数配置。如果选择了图表渲染，则可以点击图表设置来添加、删除图表和设置图表的参数，并且可以在下方看到各个变量的范围，方便选取。采点窗口中最上方为采点菜单，点击后可以设置采点的参数，下方是被采集的点的属性。最后，图像区域便是可视化结果展示的区域，也是采点时点击的区域。

\begin{figure}[H]
    \label{fig:GUI}
    \centering
    \includegraphics[width=0.6\textwidth]{./images/placeholder}
    \caption{主要界面图}
\end{figure}

\textcolor{red}{界面估计还会有改动，界面图之后再截}

\section{优化方法}
\subsection{文件读取优化}
CFD中涉及的数据文件数量级可以非常大，这会让外存读取速度成为很大的瓶颈。因此文件读取的优化十分重要，可以从多个角度进行优化。

为了便于数据读取，同时压缩体积，文件常常会使用二进制存储，而非文本格式。此外，数据常常会被分成多个区域，每个区域的数据相对独立，一个区域数据的读取不依赖于其他区域。在此之上，区域的数据还常常以“块”的形式存储，这些块中只包括记录的数据，不包含控制信息，这样在读取时可以一次取出，减少IO次数提高了效率，读取这样空间上连续分布的数据也能提高IO效率。

不仅如此，因为每个区域相互独立，软件可以先读取所需要的区域，甚至只读取所需要的几何数据和属性数据，而其他数据等到需要时再读取。因为可视化和后处理时一般不会同时用到所有的属性，这样加载方式能提升一部分效率。

最后，IO使用了异步的方式，即使IO被阻塞，也不会影响主进程的工作，虽然对效率提升不大，但能够提升用户体验。
\subsection{可视化优化}
CFD的可视化对真实感要求不高，只需要有基本的着色即可，无需纹理、投影和全局光照等渲染。但即使只有基本的着色，在面对超大规模数据时依旧会对效率有进一步的要求。要处理这类大规模数据，一个有效的方法是使用层次细节模型（Level of detail，以下简称LOD）。

LOD的基本思想是，对于一个物体，对齐进行降采样生成一个不精确的模型，再进行降采样生成更不精确的模型，重复这个操作，我们能得到多个层级的模型，这些模型的细节水平逐步下降。当相机距离物体很近时，相机可以清楚的看到物体的细节，因此使用细节水平最高的模型，也就是原始模型；而当相机距离物体很远时，相机看到物体的细节变少，因此使用细节水平低的模型对渲染的效果影响不大，但对渲染速度有很大提升；而相机和物体的距离介于中间时，便使用细节水平适中的模型。

\textcolor{red}{这里的工作还未完成。因为可视化里不是时刻渲染，加上使用低模必定导致渲染结果有误差，可能影响判断，所以使用距离决定LOD层级不是很合理。思路是，根据渲染频率来确认LOD，如果一个物体一直没有被渲染、或是很少被渲染，那么选择低模；如果一个物体一直被渲染（即相机一直盯着它看），那么渲染频率逐步上升，而LOD模型细节也逐步增多。但因为不移动相机，vtk就不渲染了，所以可能需要在actor里制作定时器，控制VTK每隔一段时间渲染一下，如果持续被渲染，就持续增大LOD细节，直到达到原始模型。}

\subsection{后处理优化}
\textcolor{red}{还未确认是否可行。}后处理的计算量庞大，而解决计算量的方法之一便是并行化。后处理算法中存在一些可并行化的算法，如在计算等势面时，可以不将整个数据顺序计算，而是可以不同区域多线程分别计算，最后对计算结果进行汇总，这可以充分利用多核处理器的性能。在计算流线时也是如此，每条流线的计算可以交给一个线程独立进行。